package com.zzyl.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.core.util.NumberUtil;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.zzyl.base.PageResponse;
import com.zzyl.dto.AlertNotifyDto;
import com.zzyl.dto.AlertRuleDto;
import com.zzyl.entity.AlertData;
import com.zzyl.entity.AlertRule;
import com.zzyl.mapper.AlertDataMapper;
import com.zzyl.mapper.AlertRuleMapper;
import com.zzyl.mapper.DeviceMapper;
import com.zzyl.mapper.UserRoleMapper;
import com.zzyl.service.AlertRuleService;
import com.zzyl.utils.ObjectUtil;
import com.zzyl.utils.StringUtils;
import com.zzyl.vo.AlertRuleVo;
import com.zzyl.vo.DeviceDataVo;
import com.zzyl.websocket.WebSocketServer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author sjqn
 */
@Slf4j
@Service
public class AlertRuleServiceImpl implements AlertRuleService {

    @Autowired
    private AlertRuleMapper alertRuleMapper;

    /**
     * 新增报警规则
     *
     * @param alertRuleDto 报警规则请求模型
     */
    @Override
    public void createRule(AlertRuleDto alertRuleDto) {
        AlertRule alertRule = BeanUtil.toBean(alertRuleDto, AlertRule.class);
        alertRuleMapper.insert(alertRule);
    }

    /**
     * 分页条件查询
     *
     * @param pageNum       页码
     * @param pageSize      页面大小
     * @param alertRuleName 报警规则名称
     * @param productKey    产品key
     * @param functionName  功能名称
     * @return 分页结果
     */
    @Override
    public PageResponse<AlertRuleVo> getAlertRulePage(Integer pageNum, Integer pageSize, String alertRuleName, String productKey, String functionName) {
        PageHelper.startPage(pageNum, pageSize);
        Page<AlertRuleVo> page = alertRuleMapper.page(alertRuleName, productKey, functionName);
        //添加提示
        page.getResult().forEach(alertRuleVo -> {
            String alertReason = CharSequenceUtil.format("{}{}{},持续{}个周期就报警",
                    alertRuleVo.getFunctionName(),
                    alertRuleVo.getOperator(),
                    alertRuleVo.getValue(),
                    alertRuleVo.getDuration());
            alertRuleVo.setRules(alertReason);
        });

        return PageResponse.of(page, AlertRuleVo.class);
    }

    /**
     * 删除报警
     *
     * @param id 报警规则id
     */
    @Override
    public void deleteAlertRule(Long id) {
        alertRuleMapper.deleteByPrimaryKey(id);
    }

    /**
     * 修改报警
     *
     * @param id           报警规则id
     * @param alertRuleDto 报警规则更新请求模型
     */
    @Override
    public void updateAlertRule(Long id, AlertRuleDto alertRuleDto) {
        AlertRule alertRule = BeanUtil.toBean(alertRuleDto, AlertRule.class);
        alertRuleMapper.updateByPrimaryKeySelective(alertRule);
    }

    /**
     * 查询报警信息
     *
     * @param id 报警规则id
     * @return 报警信息
     */
    @Override
    public AlertRuleVo readAlertRule(Long id) {
        AlertRule alertRule = alertRuleMapper.selectByPrimaryKey(id);
        return BeanUtil.toBean(alertRule, AlertRuleVo.class);
    }

    /**
     * 启用或禁用
     *
     * @param id     报警id
     * @param status 报警规则状态
     */
    @Override
    public void enableOrDisable(Long id, Integer status) {
        alertRuleMapper.updateStatus(id, status);
    }

    @Override
    public void alertFilter(DeviceDataVo deviceDataVo) {

        //1判断数据的实时性
        //1.1获得数据的触发时间，判断是否在1分钟之内
        LocalDateTime alarmTime = deviceDataVo.getAlarmTime();

        //1.2.判断是否在1分钟之内
        long between = LocalDateTimeUtil.between(alarmTime, LocalDateTime.now(), ChronoUnit.SECONDS);

        //1.3如果不在一分钟之内，直接结束
        if (between > 60) {
            return;
        }

        // 2 获得规则

        log.info("获取所有的规则，准备合并，然后匹配.....");
        // 2.1获得所有的该产品下功能的规则（心率 心跳，血氧 血压）
        List<AlertRule> alertRules1 = alertRuleMapper.selectByFunctionId(deviceDataVo.getFunctionId(),
                "-1",
                deviceDataVo.getProductKey());


        // 2.1获得该产品，某个设备的 物理模型的规则，如：心跳
        List<AlertRule> alertRules2 = alertRuleMapper.selectByFunctionId(deviceDataVo.getFunctionId(),
                deviceDataVo.getIotId(),
                deviceDataVo.getProductKey());


        // 2.3 合并规则
        Collection<AlertRule> alertAllRules = CollUtil.addAll(alertRules1, alertRules2);

        if (CollUtil.isEmpty(alertAllRules)) {
            return;
        }


        //3.获得数据，将数据和规则进行匹配---重点
        for (AlertRule alertAllRule : alertAllRules) {

            deviceDataAlarmHandler(deviceDataVo, alertAllRule);

        }

    }

    @Autowired
    private StringRedisTemplate redisTemplate;
    @Autowired
    private DeviceMapper deviceMapper;
    @Autowired
    private UserRoleMapper userRoleMapper;
    @Autowired
    private AlertDataMapper alertDataMapper;
    @Autowired
    private WebSocketServer webSocketServer;

    /**
     * 规则数据处理程序
     *
     * @param deviceDataVo 某一条数据
     * @param alertAllRule 某一条规则
     */
    private void deviceDataAlarmHandler(DeviceDataVo deviceDataVo, AlertRule alertAllRule) {

        //1. 判断生效时间  07:00:00~22:59:59
        String[] split = alertAllRule.getAlertEffectivePeriod().split("~");
        LocalTime startTime = LocalTime.parse(split[0]);
        LocalTime endTime = LocalTime.parse(split[1]);

        //获得上报时间
        LocalTime localTime = deviceDataVo.getAlarmTime().toLocalTime();
        //       05:00:00   localTime=04:00:00
        //   05:00:00                          22:59:59
        //如果在开始时间之前，直接返回，如果在结束时间之后，直接返回
        if (localTime.isBefore(startTime) || localTime.isAfter(endTime)) {
            log.info("时间不在规则生效时间之内,不会报警");
            return;
        }

        String iotId = deviceDataVo.getIotId();

        // 第二次校验，判断数据阈值
        //获取当前值和阈值的差值，可能是正数 也可以是负数，但是要集合 运算符来判断
        //compare(x,y)  如果x=y 返回compare=0 如果x>y 返回大于0--正数。如果x<y 返回负数
        int compare = NumberUtil.compare(Double.valueOf(deviceDataVo.getDataValue()), alertAllRule.getValue());

        //获取规则中的运算符  >=  或者<
        String triggerCountCacheKey = "triggercount:" + iotId + ":" + deviceDataVo.getFunctionId() + ":" + alertAllRule.getId();

        if ((alertAllRule.getOperator().equals(">=") && compare >= 0) ||
                (alertAllRule.getOperator().equals("<") && compare < 0)
        ) {
            log.info("当前设备上报的数据，符合规则，准备进一步检测！！！阈值：{}  现值：{}", alertAllRule.getValue(), deviceDataVo.getDataValue());
        } else {
            log.info("当前设备上报的数据，不符合规则，不会报警！！！阈值：{}  现值：{}", alertAllRule.getValue(), deviceDataVo.getDataValue());

            //todo 删除redis中保存的 报警数量（假设是3为周期，目前是2 ，也要删除，因为要连续）
            redisTemplate.delete(triggerCountCacheKey);
            return;
        }


        //todo 查询redis中存储的沉默周期
        //获得存在redis中的沉默周期的key：silence:设备id:功能id:规则  如：随眠带：心率：大于10
        String silenceCacheKey = "silence:" + iotId + ":" + deviceDataVo.getFunctionId() + ":" + alertAllRule.getId();

        String silenceCacheData = redisTemplate.opsForValue().get(silenceCacheKey);
//        if (StringUtils.isNotEmpty(silenceCacheData)){
//            //如果沉默数据是空的，说明可以报警，如果有值，等着沉默时间结束
//            log.info("当前设备：{}  功能：{}  规则：{}  在沉默时间内，不会报警",iotId,deviceDataVo.getFunctionId(),alertAllRule.getId());
//            return;
//        }


        log.info("当前设备：{}  功能：{}  规则：{}  不在沉默时间内，会报警", iotId, deviceDataVo.getFunctionId(), alertAllRule.getId());

        String triggerCountStr = redisTemplate.opsForValue().get(triggerCountCacheKey);
        //如果当前是2次，持续周期是3次，那么此次是第三次，应该触发
        //转换数字，初始值
        int triggerCount = StringUtils.isEmpty(triggerCountStr) ? 1 : Integer.parseInt(triggerCountStr) + 1;

        //匹配报警次数和持续周期
        if (ObjectUtil.notEqual(alertAllRule.getDuration(), triggerCount)) {
            log.info("当前设备：{}  功能：{}  规则：{}  触发次数：{}  持续周期：{}  匹配失败，不会报警",
                    iotId, deviceDataVo.getFunctionId(), alertAllRule.getAlertRuleName(), triggerCount, alertAllRule.getDuration());
            //如果匹配失败，则触发次数不够
            redisTemplate.opsForValue().set(triggerCountCacheKey, triggerCount + "");

            return;
        }

        //匹配成功，触发次数达到，可以报警，先删除redis中存储的次数

        redisTemplate.delete(triggerCountCacheKey);


        //添加沉默周期, 利用redis消息的自动销毁，完成沉默周期的需求
        redisTemplate
                .opsForValue()
                .set(silenceCacheKey,
                        deviceDataVo.getDataValue(),
                        alertAllRule.getAlertSilentPeriod(),
                        TimeUnit.MINUTES);

        //定义一个需要被通知的人的 id的集合
        List<Long> userIds = new ArrayList<>();

        //判断规则的类型
        if (alertAllRule.getAlertDataType() == 0) {
            //老人异常

            //随身设备，如手表
            if (deviceDataVo.getLocationType() == 0) {
                //通过设备ID查到用户Id
                userIds = deviceMapper.selectNursingIdsByIotIdWithElder(iotId);

            } else if (deviceDataVo.getLocationType() == 1 && deviceDataVo.getPhysicalLocationType() == 2) {
                //固定设备。如睡眠带
                userIds = deviceMapper.selectNursingIdsByIotIdWithBed(iotId);

            }
        } else {
            //设备异常,通知 维修工 （角色）来修理

            userIds = userRoleMapper.selectUserIdByRoleName("维修工");
        }

//        超级管理员

        List<Long> adminuserIds = userRoleMapper.selectUserIdByRoleName("超级管理员");

        //目前有2个员工id的集合，可能出现重复
        List<Long> allUserIds = CollUtil.addAllIfNotContains(adminuserIds, userIds);

        allUserIds = CollUtil.distinct(allUserIds);

        //给allUserIds发送报价数据
        insertAlertData(allUserIds, alertAllRule, deviceDataVo);

        //给管理端发送消息，提示来人


        sendMessageToUser(alertAllRule, deviceDataVo,allUserIds);


    }

    /**
     * 给用户发送消息
     * @param alertAllRule
     * @param deviceDataVo
     * @param allUserIds
     */
    private void sendMessageToUser(AlertRule alertAllRule,
                                   DeviceDataVo deviceDataVo,
                                   List<Long> allUserIds) {

        log.info("WebSocket给用户发送消息   ");

        AlertNotifyDto alertNotifyDto = new AlertNotifyDto();
        alertNotifyDto.setNotifyType(1);//报警通知类型，0：解除报警，1：报警
        alertNotifyDto.setVoiceNotifyStatus(1);//语音通知状态，0：不通知，1：通知
        alertNotifyDto.setAlertDataType(alertAllRule.getAlertDataType());

        alertNotifyDto.setLocationType(deviceDataVo.getLocationType());
        alertNotifyDto.setPhysicalLocationType(deviceDataVo.getPhysicalLocationType());
        alertNotifyDto.setFunctionName(deviceDataVo.getFunctionId());
        alertNotifyDto.setDataValue(deviceDataVo.getDataValue());
        alertNotifyDto.setId(alertAllRule.getId());
        alertNotifyDto.setAccessLocation(deviceDataVo.getAccessLocation());
        alertNotifyDto.setProductName(deviceDataVo.getProductName());

        webSocketServer.sendMessageToConsumer(alertNotifyDto, allUserIds);
    }


    /**
     * 批量插入报警数据
     *
     * @param allUserIds
     * @param alertAllRule
     * @param deviceDataVo
     */
    private void insertAlertData(List<Long> allUserIds, AlertRule alertAllRule, DeviceDataVo deviceDataVo) {

        //1.新建报警数据
        AlertData alertData = BeanUtil.toBean(deviceDataVo, AlertData.class);

        //1.1 设置规则id
        alertData.setAlertRuleId(alertAllRule.getId());

        //1.2设置报警原因
        //Hearrate >70 持续了 几个周期，可以报警了
        String reason = alertAllRule.getAlertRuleName() + "：" + alertAllRule.getFunctionName() + "：" + alertAllRule.getOperator() + alertAllRule.getValue() + " 需要报警！！！";
        alertData.setAlertReason(reason);

        alertData.setStatus(0);

        alertData.setType(alertAllRule.getAlertDataType());

        //2.将这一条报警数据，发送给allUserIds人。

//        allUserIds---5个人--->5条alertData数据
        List<AlertData> alertDataList = allUserIds.stream().map(userId -> {
            //拷贝alertData
            AlertData bean = BeanUtil.toBean(alertData, AlertData.class);
            bean.setUserId(userId);
            return bean;
        }).collect(Collectors.toList());

        alertDataMapper.batchInsert(alertDataList);
    }
}
